package game.Weapons;

import com.sun.javafx.geom.Vec2d;
import game.Configuration;
import game.Gamemode.GamemodeManager;
import game.Gamemode.TeamDeathmatchManager;
import game.Map.Map;
import game.Player.Damageable;
import game.Player.Player;
import game.Rendering.PointLight;
import game.Sprites.*;
import game.Vector3;
import  game.Scene;
import javafx.scene.canvas.GraphicsContext;
import javafx.util.Pair;

import javafx.scene.paint.Color;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

public class Bullet extends Sprite  {
    protected Vector3 position;         // Bullet coordinates
    protected Vector3 lastPosition; // Used to paint bullet trace
    protected double rotation;          // Bullet direction
    protected Color color;              // color
    protected Player playerOwner;       // Player who owns the bullet
    protected int damage;               // Damage caused by the bullet
    protected double bulletRange;       // Range in meters
    protected double bulletSpeed;       // Speed of bullet;
    protected Weapon_info weapon;

    protected Vector3 initialPosition;


    protected Map map;


    public Bullet(Vector3 position, double rotation, Color color, Player playerOwner, int damage, int bulletSpeed,
                  double bulletRange) throws Exception {
        super(position, rotation, color);
        this.position = position;
        this.initialPosition = position;
        this.lastPosition = position;
        this.rotation = rotation;
        this.color = color;
        this.playerOwner = playerOwner;
        this.damage = damage;
        this.bulletRange = bulletRange;
        this.bulletSpeed = bulletSpeed;
        this.weapon = playerOwner.current_weapon;

        this.map = Scene.getInstance().getMap();
    }

    protected static Point2D intersection(Line2D lineA, Line2D lineB)
    {
        double x1 = lineA.getX1();
        double y1 = lineA.getY1();

        double x2 = lineA.getX2();
        double y2 = lineA.getY2();

        double x3 = lineB.getX1();
        double y3 = lineB.getY1();

        double x4 = lineB.getX2();
        double y4 = lineB.getY2();

        double d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4);
        if (d == 0) return null;

        double xi = ((x3-x4)*(x1*y2-y1*x2)-(x1-x2)*(x3*y4-y3*x4))/d;
        double yi = ((y3-y4)*(x1*y2-y1*x2)-(y1-y2)*(x3*y4-y3*x4))/d;
        return new Point2D.Double(xi,yi);
    }

    protected static Point2D getIntersectionPoint(Line2D line, Rectangle2D rectangle) {
        Point2D[] p1 = new Point2D[4];

        // Top line
        p1[0] = intersection(line,
                new Line2D.Double(
                        rectangle.getX(),
                        rectangle.getY(),
                        rectangle.getX() + rectangle.getWidth(),
                        rectangle.getY()));
        // Bottom line
        p1[1] = intersection(line,
                new Line2D.Double(
                        rectangle.getX(),
                        rectangle.getY() + rectangle.getHeight(),
                        rectangle.getX() + rectangle.getWidth(),
                        rectangle.getY() + rectangle.getHeight()));
        // Left side...
        p1[2] = intersection(line,
                new Line2D.Double(
                        rectangle.getX(),
                        rectangle.getY(),
                        rectangle.getX(),
                        rectangle.getY() + rectangle.getHeight()));
        // Right side
        p1[3] = intersection(line,
                new Line2D.Double(
                        rectangle.getX() + rectangle.getWidth(),
                        rectangle.getY(),
                        rectangle.getX() + rectangle.getWidth(),
                        rectangle.getY() + rectangle.getHeight()));

        double minDist = -1;
        Point2D res = null;
        for (Point2D point: p1) {
            if(point != null){
                double dist = point.distance(line.getP1());
                if(minDist > dist || res == null){
                    minDist = dist;
                    res = point;
                }
            }
        }
        return res;
    }

    public void update(double deltaTime) {
        boolean wallHit = false;
        boolean playerHit = false;
        Point2D hitPoint = null;

        boolean destroyBullet = false;      // Para destruir o no la bala

        // Destroy when range reached
        if(Vector3.distance(initialPosition, position) >= bulletRange){
            this.destroy();
        }

        this.lastPosition = this.position;

        // Por si acaso por ahora comprobar si la bala se sale del rango del mapa y eliminarla
        if(this.position.x <= 0 || this.position.x >= this.map.getNumberofTilesWidth()) {
            destroyBullet = true;
        }
        if(this.position.y <= 0 || this.position.y >= this.map.getNumberofTilesHeight()) {
            destroyBullet = true;
        }

        // Divide movement in multiple parts (better collision detection)
        int timeDivisions = 20;
        double deltaTimeFraction = deltaTime / timeDivisions;
        for(int timI = 0; timI < timeDivisions && !playerHit && !wallHit; timI++){
            // Calcular posicion siguiente de la bala
            Vector3 newPosBullet = this.position.add(
                    new Vector3(Math.cos(this.rotation), Math.sin(this.rotation), 0.0).multiply(bulletSpeed*deltaTimeFraction));


            // Comprobar colision con paredes
            Point2D wallCol = this.checkWallCollision(this.position, newPosBullet);
            double wallDist = 0.0;
            if(wallCol != null){
                //Calcular distancia pared
                wallDist = wallCol.distance(new Point2D.Double(this.getX(), this.getY()));
            }

            // Comprobar colision con jugadores
            Pair<Player, Point2D> playerCol = this.checkPlayerCollision(this.position, newPosBullet);
            Player hitPlayer = playerCol.getKey();
            Point2D playerPoint = playerCol.getValue();
            double playerDist = 0.0;

            // Colision con pared
            if(wallCol != null && (hitPlayer == null || playerDist >= wallDist) ){
                wallHit = true;
                hitPoint = wallCol;
            }
            // Colision con jugador
            else if(hitPlayer != null && (wallCol == null || playerDist < wallDist) ){
                playerHit = GamemodeManager.getInstance().bulletHit(this, hitPlayer);
                if(playerHit){
                    hitPoint = playerPoint;
                    //TODO Fix bad collision point with player
                    hitPoint = new Point2D.Double(hitPlayer.getX(), hitPlayer.getY());
                }

                if(!playerHit && wallCol != null){
                    // Colision con pared
                    wallHit = true;
                    hitPoint = wallCol;
                }
            }

            // Destruir al chocar con pared
            if(wallHit) {
                this.destroy();
            }

            // Actualizar posicion
            this.position = newPosBullet;
        }

        if(hitPoint != null){
            Vector3 hitPos = new Vector3(hitPoint.getX(), hitPoint.getY(), 0.0);
            if(playerHit){
                // Create blood
                if(Configuration.isBlood()) {
                    try {
                        for (int i = 0; i < 2; i++) {
                            double randRot = (Math.random() - 0.5) * 0.15;
                            new BloodSprite(hitPos, this.rotation + randRot);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    try {
                        for (int i = 0; i < 1; i++) {
                            double randRot = (Math.random() - 0.5) * 0.35;
                            new BloodSplash(hitPos, this.rotation + randRot);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }else if(wallHit){
                // Create wall debris
                try {
                    for(int i = 0; i < 3; i++){
                        double randRot = (Math.random()-0.5) * 0.15;
                        new WallDebry(hitPos, this.rotation + Math.PI + randRot);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        createBulletTray();

    }

    /**
     * Creates a visible tray behind the bullet
     */
    public void createBulletTray(){
        double dist = Vector3.distance(position, lastPosition);
        Vector3 movementVector = position.sub(lastPosition);

        double drawSpace = 0.25;
        double AcummDist = (Math.random()) * drawSpace + drawSpace;
        while(AcummDist < dist){
            Vector3 sparkPos = lastPosition.add(movementVector.multiply(AcummDist/dist));

            double rotationDist = (Math.random() - 0.5)*0.1;
            double sideRot = rotation + Math.PI/2;
            sparkPos = sparkPos.add(new Vector3(Math.cos(sideRot) * rotationDist, Math.sin(sideRot) * rotationDist, 0.0));

            try {
                new Spark(sparkPos, rotation, Color.YELLOW, 0.1, 1.0);
            } catch (Exception e) {
                e.printStackTrace();
            }
            AcummDist += (Math.random()) * drawSpace + drawSpace;
        }

    }

    public Point2D checkWallCollision(Vector3 oldPos, Vector3 newPos){
        boolean hitWall = false;            // Para saber si impacta o no con un muro
        Point2D wallPoint = null;

        // Recta que traza el disparo
        Line2D line = new Line2D.Double(new Point2D.Double(oldPos.x,oldPos.y),
                new Point2D.Double(newPos.x,newPos.y));

        // Comprobar si entre la posicion actual y la futura hay muro
        Vector3 choqueMuro = this.map.wall_between(oldPos, newPos);
        if(choqueMuro != null) {
            hitWall = true;

            // Calcular punto de colision con muro
            wallPoint = getIntersectionPoint(line, new Rectangle2D.Double(choqueMuro.x, choqueMuro.y, 1.0, 1.0));
        }

        return wallPoint;
    }

    public Pair<Player, Point2D> checkPlayerCollision(Vector3 oldPos, Vector3 newPos){
        Player hitPlayer = null;
        Point2D hitPoint = null;

        // Radio de colision con jugadores
        double radius = 0.9;

        // Recta que traza el disparo
        Line2D line = new Line2D.Double(new Point2D.Double(oldPos.x,oldPos.y),
                new Point2D.Double(newPos.x,newPos.y));

        // Comprobar colision con jugadores y devolver el mas cercano
        List<Player> playerList = TeamDeathmatchManager.getInstance().getPlayerList();
        double minDist = 0.0;
        for(int i=0; i<playerList.size(); i++) {
            // Skip collision with player owner and dead bodies
            Player pl = playerList.get(i);
            if(pl == playerOwner || !pl.isAlive()){
                continue;
            }

            // Comprobar colision con jugador
            if(line.ptSegDist(new Point2D.Double(pl.getX(), pl.getY())) <= radius) {
                double distancePlayer = Vector3.distance(oldPos, pl.getPosition());
                if(distancePlayer < minDist || hitPlayer == null){
                    hitPlayer = pl;
                    minDist = distancePlayer;

                    hitPoint = new Point2D.Double(
                            oldPos.x + Math.cos(this.rotation)*distancePlayer,
                            oldPos.y + Math.sin(this.rotation)*distancePlayer);
                }
            }
        }

        return new Pair<Player, Point2D>(hitPlayer, hitPoint);
    }

    /**
     * Draw bullet
     * @return
     */
    public void draw(GraphicsContext g2D, Vector3 screenPos, double squareRes) {
        double theta = this.getRotation();
        //g2D.setColor(this.color);

        int traceLength = (int)(Vector3.distance(lastPosition, position) * squareRes);
        //g2D.rotate(theta, (int) screenPos.x, (int) screenPos.y);
        //g2D.fillRect((int) screenPos.x - traceLength, (int) screenPos.y - 1, traceLength, 3);
        //g2D.rotate(-theta, (int) screenPos.x, (int) screenPos.y);
    }

    /**
     *
     * @return a rectangle
     */
    public Rectangle2D.Double getBound() {  // HABRA QUE CAMBIARLO
        if(this.position == null)
            return null;

        return new Rectangle2D.Double(this.position.x - 0.5, this.position.y - 0.5, 0.1, 0.8);
    }


    public Vector3 getPosition() {
        return position;
    }

    public void setPosition(Vector3 position) {
        this.position = position;
    }

    public double getRotation() {
        return rotation;
    }

    public void setDirection(double rotation) {
        this.rotation = rotation;
    }

    public Player getPlayer() {
        return playerOwner;
    }

    public void setPlayerOwner(Player playerOwner) {
        this.playerOwner = playerOwner;
    }

    public int getDamage() {
        return damage;
    }

    public void setDamage(int damage) {
        this.damage = damage;
    }

    //TODO
    public void hit(Damageable obj){
        obj.doDamage(playerOwner, this.weapon, damage);
        this.destroy();
    }

    @Override
    public void destroy(){
        super.destroy();

    }
}
